package com.sunday.gateway.core.filter.log;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.filter.factory.CacheRequestBodyGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.lang.reflect.Field;
import java.net.URI;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR;

/**
 * 访问日志过滤器
 *
 * @author zsy
 * @see ModifyResponseBodyGatewayFilterFactory.ModifyResponseGatewayFilter
 * @see CacheRequestBodyGatewayFilterFactory
 * @since 2024/1/31
 */
@Slf4j
public class AccessLogGatewayFilter implements GatewayFilter, Ordered {

    private final AccessLogGatewayFilterFactory.Config config;

    private final List<HttpMessageReader<?>> messageReaders;

    private GatewayFilterFactory<AccessLogGatewayFilterFactory.Config> gatewayFilterFactory;

    static final String CACHED_ORIGINAL_REQUEST_BODY_BACKUP_ATTR = getStaticFieldValue(CacheRequestBodyGatewayFilterFactory.class, "CACHED_ORIGINAL_REQUEST_BODY_BACKUP_ATTR");

    public AccessLogGatewayFilter(AccessLogGatewayFilterFactory.Config config) {
        this.config = config;
        this.messageReaders = HandlerStrategies.withDefaults().messageReaders();
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();
        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);

        AccessLog accessLog = new AccessLog();
        /** 访问实例 */
        accessLog.setTargetServer(route.getId());
        /** 请求路径 */
        accessLog.setRequestPath(request.getPath().pathWithinApplication().value());
        /** 请求方法 */
        accessLog.setRequestMethod(request.getMethod().toString());
        /** 协议 */
        accessLog.setSchema(request.getURI().getScheme());
        /** 媒体类型 */
        accessLog.setMediaType(Optional.ofNullable(request.getHeaders().getContentType()).map(MediaType::toString).orElse(null));
        /** 请求时间 */
        accessLog.setRequestTime(LocalDateTime.now());
        /** 请求ip */
        accessLog.setIp(request.getRemoteAddress().getAddress().getHostAddress());
        /** 可能存在的用户 **/
        maybeUser(request, accessLog);

        /** 媒体类型 */
        return Optional.ofNullable(request.getHeaders().getContentType())
                // 判断 MediaType 是否为 application/x-www-form-urlencoded 或 application/json
                .filter(mediaType -> config.getSupportedRequestMediaTypes().stream()
                        .anyMatch(supportedMediaType -> supportedMediaType.isCompatibleWith(mediaType)))
                .map(_ -> handleReadRequestBody(exchange, chain, accessLog))
                .orElseGet(() -> handleReadRawQuery(exchange, chain, accessLog));
    }

    /**
     * 处理请求体-原始查询
     *
     * @param exchange
     * @param chain
     * @param accessLog
     * @return {@link Mono< Void>}
     */
    public Mono<Void> handleReadRawQuery(ServerWebExchange exchange, GatewayFilterChain chain, AccessLog accessLog) {
        /** 请求体 */
        accessLog.setRequestBody(exchange.getRequest().getURI().getRawQuery());
        // 自定义ServerHttpResponseDecorator以修改响应体
        ServerHttpResponseDecorator decoratedResponse = new AccessLogHttpResponse(exchange, config, accessLog);
        // 重新构建一个 ServerWebExchange，用新的 request
        ServerWebExchange mutatedExchange = exchange.mutate().response(decoratedResponse).build();
        return chain.filter(mutatedExchange)
                .doOnError(throwable -> error(throwable, accessLog))
                .then(log(accessLog));
    }


    /**
     * 处理请求体-获取请求体内容
     *
     * @param exchange
     * @param chain
     * @param accessLog
     * @return {@link Mono< Void>}
     * @see CacheRequestBodyGatewayFilterFactory
     */
    public Mono<Void> handleReadRequestBody(ServerWebExchange exchange, GatewayFilterChain chain, AccessLog accessLog) {

        ServerHttpRequest request = exchange.getRequest();
        URI requestUri = request.getURI();
        String scheme = requestUri.getScheme();

        // 兼容 org.springframework.cloud.gateway.filter.factory.CacheRequestBodyGatewayFilterFactory
        boolean flag;
        Object cachedBody;
        // Record only http requests (including https)
        if (flag = (!"http".equals(scheme) && !"https".equals(scheme))) {
            accessLog.setRequestBody("record only http requests (including https)");
        }
        // 判断请求体大小是否小于 maxSize
        // 后续得方法中 DataBufferUtils.join 会将整个请求体读取到内存中，不论其大小如何，这可能对于处理大量的并发请求或非常大的请求体时导致内存压力增大。为了避免这种情况，读取时加入限制。
        else if (flag = (exchange.getRequest().getHeaders().getContentLength() > config.getMaxRequestSize().toBytes())) {
            accessLog.setRequestBody(STR."request body is too large, max size \{config.getMaxRequestSize().toBytes()}B");
        }
        // 判断是否已经缓存了请求体 兼容 CacheRequestBodyGatewayFilterFactory
        else if (flag = ((cachedBody = exchange.getAttribute(ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR)) != null)) {
            accessLog.setRequestBody(cachedBody.toString());
        }

        if (flag) {
            // 重新构建一个 ServerWebExchange，用新的 request
            ServerWebExchange mutatedExchange = exchange.mutate()
                    .response(new AccessLogHttpResponse(exchange, config, accessLog))
                    .build();
            return chain.filter(mutatedExchange)
                    .doOnError(throwable -> error(throwable, accessLog))
                    .then(log(accessLog));
        }

        /**
         * ServerWebExchangeUtils.cacheRequestBodyAndRequest 方法通过将请求体数据读取进内存，
         * 并在ServerWebExchange的属性中缓存这些数据，让后续的处理逻辑可以从缓存中读取请求体，而不是直接从一次性的数据流中读取。
         * 这也意味着需要额外的内存来存储这些数据，并且可能需要考虑到大请求体引发的内存使用问题。
         */
        return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> {
            // 创建新的 ServerRequest 以读取 body 数据
            final ServerRequest serverRequest = ServerRequest
                    .create(exchange.mutate().request(serverHttpRequest).build(), messageReaders);

            // 读取请求体，并将其转化为配置的目标类对象的 Mono
            return serverRequest.bodyToMono(config.getBodyClass())
                    // 当请求体被读取后，对其进行缓存
                    .doOnNext(objectValue -> {
                        /** 请求体 */
                        accessLog.setRequestBody(objectValue.toString());
                        // 将读取到的对象放入 exchange 的 attributes 中缓存
                        Object previousCachedBody = exchange.getAttributes()
                                .put(ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR, objectValue);

                        // 如果之前已经缓存过请求体，则备份之前的缓存
                        if (previousCachedBody != null) {
                            exchange.getAttributes()
                                    .put(CACHED_ORIGINAL_REQUEST_BODY_BACKUP_ATTR, previousCachedBody);
                        }
                    }).then(Mono.defer(() -> {
                        // 从 attributes 中获取缓存的 ServerHttpRequest 对象
                        ServerHttpRequest cachedRequest = exchange
                                .getAttribute(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR);

                        // 确保缓存的请求对象不为空
                        Assert.notNull(cachedRequest, "cache request shouldn't be null");

                        // 移除使用完成的缓存请求修饰器属性
                        exchange.getAttributes().remove(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR);

                        // 重新构建一个 ServerWebExchange，用新的 request
                        ServerWebExchange mutatedExchange = exchange.mutate()
                                .request(cachedRequest)
                                .response(new AccessLogHttpResponse(exchange, config, accessLog)).build();

                        // 使用缓存的请求对象重新发起 filter 链处理，并构建新的 ServerWebExchange
                        return chain.filter(mutatedExchange)
                                // 不管结果如何，最终都会执行此处的代码
                                .doFinally(_ -> {
                                    // 清理操作，释放 DataBuffer 资源
                                    Object backupCachedBody = exchange.getAttributes()
                                            .get(CACHED_ORIGINAL_REQUEST_BODY_BACKUP_ATTR);
                                    // 如果备份的请求体是 DataBuffer 类型，则进行释放
                                    if (backupCachedBody instanceof DataBuffer dataBuffer) {
                                        DataBufferUtils.release(dataBuffer);
                                    }
                                })
                                .doOnError(throwable -> error(throwable, accessLog))
                                .then(log(accessLog));
                    }));
        });
    }

    /**
     * 保留最终结果日志
     *
     * @param accessLog
     * @return {@link Mono}
     */
    public Mono log(AccessLog accessLog){
        return Mono.fromRunnable(() -> log.info("{}", accessLog));
    }

    /**
     * 错误日志
     *
     * @param throwable
     * @param accessLog
     * @return
     */
    public void error(Throwable throwable, AccessLog accessLog) {
        /** 响应时间 */
        accessLog.setResponseTime(LocalDateTime.now());
        /** 执行时间 */
        long executeTime = Duration.between(accessLog.getRequestTime(), accessLog.getResponseTime()).toMillis();
        accessLog.setExecuteTime(executeTime);
        /** 响应体 */
        accessLog.setResponseBody(throwable.toString());
    }

    private void maybeUser(ServerHttpRequest request, AccessLog accessLog) {

    }

    /**
     * 获取过滤器的顺序
     * 参考 {@link ModifyResponseBodyGatewayFilterFactory.ModifyResponseGatewayFilter#getOrder()}
     * 务必要在 {@link NettyWriteResponseFilter} 之后
     *
     * @param
     * @return {@link int}
     * @see ModifyResponseBodyGatewayFilterFactory.ModifyResponseGatewayFilter#getOrder()
     */
    @Override
    public int getOrder() {
        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
    }

    @Override
    public String toString() {
        Object obj = (this.gatewayFilterFactory != null) ? this.gatewayFilterFactory : this;
        return filterToStringCreator(obj)
                .append("Maximum request body size", config.getMaxRequestSize())
                .append("Maximum response body size", config.getMaxResponseSize())
                .append("Supported request content types", config.getSupportedRequestMediaTypes())
                .append("Supported response content types", config.getSupportedResponseMediaTypes())
                .append("order", this.getOrder())
                .toString();
    }

    public void setFactory(GatewayFilterFactory<AccessLogGatewayFilterFactory.Config> gatewayFilterFactory) {
        this.gatewayFilterFactory = gatewayFilterFactory;
    }

    /**
     * 获取静态字段的值
     *
     * @param clazz
     * @param fieldName
     * @return {@link String}
     */
    public static String getStaticFieldValue(Class<?> clazz, String fieldName) {
        try {
            Field field = clazz.getDeclaredField(fieldName); // 直接从类中获取声明的字段
            field.setAccessible(true); // 确保case字段的可访问性是私有的或受保护的
            // field.get(null);中传递null作为参数是因为想要获取的字段field是静态的。在Java的反射API中，如果你要通过Field对象获取一个静态字段的值，你不需要提供一个对象的实例，因为静态字段不是属于任何一个实例，而是属于类本身。
            return (String) field.get(null); // 将值强制转换为String，因为我们期望String返回类型
        } catch (NoSuchFieldException e) {
            throw new IllegalArgumentException(STR."\{clazz.getName()} does not have a field named \{fieldName}", e);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException(STR."Failed to access field \{fieldName} of \{clazz.getName()}", e);
        }
    }

}
